package com.fintech.pangu.oauth2.config;

import com.fintech.pangu.autoconfigure.security.PanGuWebSecurityConfigurationAdapter;
import com.fintech.pangu.autoconfigure.security.properties.PanGuSecurityProperties;
import com.fintech.pangu.oauth2.authentication.handler.FormAuthenticationFailureHandler;
import com.fintech.pangu.oauth2.logout.OAuth2SsoServerNoticeLogoutSuccessHandler;
import com.fintech.pangu.security.authorize.AuthorizeConfigManager;
import com.fintech.pangu.security.config.PanGuAuthenticationManagerBuilderManager;
import com.fintech.pangu.security.config.PanGuHttpSecurityManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

/**
 * 授权服务器Web安全配置
 */
@Order(SecurityProperties.BASIC_AUTH_ORDER - 6)
@Configuration
@EnableWebSecurity
@EnableConfigurationProperties(PanGuSecurityProperties.class)
public class WebSecurityConfig extends PanGuWebSecurityConfigurationAdapter {

    /**
     * 处理 POST /ssoAutoLogout 的Filter
     */
    @Autowired
    private LogoutFilter oauth2SsoServerAutoLogoutFilter;

    // 读写混合TokenStore
    @Autowired
    private TokenStore readWriteCompositeTokenStore;


    public WebSecurityConfig(
            PanGuSecurityProperties panGuSecurityProperties,
            PasswordEncoder passwordEncoder,
            ObjectProvider<PanGuHttpSecurityManager> panGuHttpSecurityManager,
            ObjectProvider<AuthorizeConfigManager> authorizeConfigManager,
            ObjectProvider<PanGuAuthenticationManagerBuilderManager> panGuAuthenticationManagerBuilderManager) {
        super(panGuSecurityProperties, passwordEncoder, panGuHttpSecurityManager.getIfAvailable(), authorizeConfigManager.getIfAvailable(), panGuAuthenticationManagerBuilderManager.getIfAvailable());
    }



    @Override
    protected void customHttpSecurity(HttpSecurity http) throws Exception {
        http
            .formLogin()                // SSO Server端的表单登录
                .loginPage("/toLogin")
                .loginProcessingUrl("/login")
                .permitAll()
                .failureHandler(authenticationFailureHandler())  // 表单登录认证失败处理器
                .and()
            .logout()
                .logoutUrl("/logout")  // SSO Server端的普通登出
                .logoutSuccessHandler(oauth2SsoServerNoticeLogoutSuccessHandler())  // 普通登出后，通知所有SSO Client自动登出
                .permitAll()
                .and()
            .addFilterBefore(oauth2SsoServerAutoLogoutFilter, LogoutFilter.class); // SSO Server端自动登出filter，/ssoAutoLogout

        // 添加跳转到 pangu.oauth2.sso.client.loginPath 的 AuthenticationEntryPoint
        // 改为使用Ajax公共EntryPoint
        //addAuthenticationEntryPoint(http);

        // 添加 OAuth2AuthenticationProcessingFilter，用于在访问受保护资源前校验access_token
        configOAuth2ResourceProtection(http);
    }


    /**
     * 表单登录认证失败处理器
     */
    public FormAuthenticationFailureHandler authenticationFailureHandler(){
        FormAuthenticationFailureHandler formAuthenticationFailureHandler = new FormAuthenticationFailureHandler();
        formAuthenticationFailureHandler.setDefaultFailureUrl("/toLogin");
        formAuthenticationFailureHandler.setUseForward(true);
        return formAuthenticationFailureHandler;
    }


    /**
     * SSO Server登出后通知所有的客户端自动登出的成功处理器
     * @return
     */
    @Bean
    public LogoutSuccessHandler oauth2SsoServerNoticeLogoutSuccessHandler(){
        return new OAuth2SsoServerNoticeLogoutSuccessHandler();
    }


    /**
     * OAuth2登录的AuthenticationEntryPoint
     * @param httpSecurity
     * @throws Exception
     */
    //private void addAuthenticationEntryPoint(HttpSecurity httpSecurity) throws Exception {
    //    ExceptionHandlingConfigurer<HttpSecurity> exceptions = httpSecurity.exceptionHandling();
    //    ContentNegotiationStrategy contentNegotiationStrategy = httpSecurity.getSharedObject(ContentNegotiationStrategy.class);
    //    if (contentNegotiationStrategy == null) {
    //        contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
    //    }
    //    MediaTypeRequestMatcher preferredMatcher = new MediaTypeRequestMatcher(
    //            contentNegotiationStrategy, MediaType.APPLICATION_XHTML_XML,
    //            new MediaType("image", "*"), MediaType.TEXT_HTML, MediaType.TEXT_PLAIN);
    //    preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
    //
    //    exceptions.defaultAuthenticationEntryPointFor(
    //            //new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
    //            new UnauthorizedEntryPoint(),
    //            new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
    //    // When multiple entry points are provided the default is the first one
    //}


    /**
     * 配置 OAuth2AuthenticationProcessingFilter，校验access_token，保护userInfo端点
     * 类似 @EnableResourceServer 作用
     * @param http
     * @throws Exception
     */
    private void configOAuth2ResourceProtection(HttpSecurity http) throws Exception {
        ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();
        resources.tokenStore(readWriteCompositeTokenStore);
        resources.stateless(false); // true 会清空SecurityContext

        http.apply(resources);
    }



    /**
     * 自定义基于内存的UserDetailsService，用于测试
     */
    @Bean
    public UserDetailsService customUserDetailsService() {
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        inMemoryUserDetailsManager.createUser(User.withUsername("admin")
                .password(getPasswordEncoder().encode("admin"))
                .authorities("ADMIN").build());

        return inMemoryUserDetailsManager;
    }


    /**
     * AuthenticationManager认证管理器配置
     * @param auth
     * @throws Exception
     */
    @Override
    protected void customAuthenticationManagerBuilder(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .userDetailsService(customUserDetailsService())
            .passwordEncoder(getPasswordEncoder());
    }


    //@Autowired
    //public void globalUserDetails(final AuthenticationManagerBuilder auth) throws Exception {
	 //   auth.inMemoryAuthentication()
	 //       .withUser("user").password(passwordEncoder().encode("password")).roles("USER")
	 //       .and()
    //        .withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN");
    //}

    /**
     * 将configure(AuthenticationManagerBuilder)配置的local认证管理器注册为Spring的Bean
     * 在配置授权服务器时需要AuthenticationManager，用于OAuth2密码模式
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}
